
/**
 * @author 徐嘉正
 * @description 常用前端工具方法汇总
 * @date 2018.4.4
 */
; (function (global, factory) {
    typeof module !== 'undefined' && typeof module.exports === 'object' ?
        module.exports = factory() : (global.memUtils = factory());
})(this, function () {
    // serizlize data
    var serialize = function (data) {
        var str = '';
        for (var key in data) {
            str += `${key}=${data[key]}&`;
        }
        return str.slice(0, -1);
    };

    // parse url
    var parseUrl = function (url) {
        let dom = document.createElement('a');
        dom.href = url;

        let keys = [
            'protocol',
            'host',
            'hostname',
            'origin',
            'pathname',
            'port',
            'search',
            'hash',
        ];
        let urlObj = {
            params: {},
        };
        keys.forEach((key) => {
            urlObj[key] = dom[key];
        });
        // 解析params
        var search = urlObj.search;
        if (search) {
            var kvArr = search.slice(1).split('&').filter(item => item);
            urlObj.params = kvArr.reduce(function (obj, item) {
                var kv = item.split('=');
                try {
                    var key = decodeURIComponent(kv[0]);
                    var value = decodeURIComponent(kv[1]);
                } catch (e) {
                    var key = kv[0];
                    var value = kv[1];
                }

                var objValue = obj[key];
                if (objValue) {
                    obj[key] = typeof objValue === 'string' ? [objValue, value] :
                        objValue.push(value);
                } else {
                    obj[key] = value;
                }
                return obj;
            }, {});
        }

        return urlObj;
    };

    // handle cookie function
    var _getExpiresTime = function (day) {
        var now = Date.now();
        var expiresTime = day * 24 * 3600 * 1000;
        var expiresDate = new Date(now + expiresTime);
        return expiresDate.toUTCString();
    }
    var _formatCookie = function (key, value, options) {
        key = encodeURIComponent(key);
        value = encodeURIComponent(value);
        var domain = options.domain ? `domain=${options.domain}` : '';
        var path = `path=${options.path || '/'}`;
        var expires = options.expires ? 'expires=' + _getExpiresTime(options.expires) : '';

        return [`${key}=${value}`, expires, domain, path].join(';');
    }
    // cookie
    var cookie = function (key, value, options) {
        var execRes;

        options = options || {};

        if (!key) {
            return null;
        }

        if (typeof value !== 'undefined') {
            // remove cookie
            if (value === null) {
                options.expires = -1;
            }

            document.cookie = _formatCookie(key, value, options);
        }
        return (execRes = new RegExp(`(?:^|;)\\s?${encodeURIComponent(key)}=([^;]*)`).exec(document.cookie)) ? decodeURIComponent(execRes[1]) : null;
    };
    /**
     * 将传递的数字当作毫秒数， 返回一个日期数据键值对。
     * @param {number} timestamp 传递一个毫秒级数字
     */
    var formatTimeJson = function (timestamp) {
        var use10MsTime = ~~(timestamp / 100); // 精确到秒后一位小数点
        var countNum;
        var middTime;
        var dateMsArr = [
            {
                key: 'yyyy',
                secs: 315360000,
                max: Infinity,
            },
            {
                key: 'MM',
                secs: 26280000,
                max: 12,
            },
            {
                key: 'dd',
                secs: 864000,
                max: 30,
            },
            {
                key: 'hh',
                secs: 36000,
                max: 24,
            },
            {
                key: 'mm',
                secs: 600,
                max: 60
            },
            {
                key: 'ss',
                secs: 10,
                max: 60
            },
            {
                key: 'ms',
                secs: 1,
                max: 10
            }
        ];
        var countObj = {
            yyyy: 0,
            MM: 0,
            dd: 0,
            hh: 0,
            mm: 0,
            ss: 0,
            ms: 0 // 最大为10
        };
        // 从最小单位开始计算，return值终止遍历
        var dataObj = dateMsArr.reverse().some(function (item) {
            countNum = ~~(use10MsTime / item.secs);

            if (countNum >= item.max) {
                countObj[item.key] = (countNum - item.max) % item.max;
            } else {
                countObj[item.key] = countNum;
                return true;
            }
        });
        return countObj;
    }
    /**
     * 倒计时函数，回调函数接收当前倒计时内容
     * @param {number/Date} timestamp 时间戳
     * @param {function} handler 倒计时回调函数
     */
    var countDown = function (timestamp, handler) {
        var interval = 100;
        var now = Date.now();
        var timer;
        var middTime;
        var initCountObj = {
            yyyy: 0,
            MM: 0,
            dd: 0,
            hh: 0,
            mm: 0,
            ss: 0,
            ms: 0 // 最大为10
        };

        if (now >= timestamp) {
            throw new Error('传入时间戳需大于当前时间');
        }

        timer = setInterval(function () {
            now = Date.now();
            middTime = timestamp - now;
            if (middTime <= 0) {
                clearInterval(timer);
                typeof handler === 'function' && handler(initCountObj, true);
            } else {
                var countObj = formatTimeJson(middTime);
                typeof handler === 'function' && handler(countObj);
            }
        }, interval);
        return timer;
    };
    /**
     * 本地存储操作，传入要存储的类型session或者local，返回一个操作对象
     * @param {string} type 传入session或者local
     */
    var storage = function (type) {
        var typeArr = ['session', 'local'];
        type = typeArr.indexOf(type) > -1 ? type : 'local';
        var storageObj = window[type + 'Storage'];
        var prefix = '_storage_';

        return {
            setPrefix: function (pref) {
                prefix = pref;
            },
            get: function (key) {
                key = prefix + key;
                var storageData = storageObj.getItem(key);
                if (!storageData) {
                    return null;
                }
                storageData = JSON.parse(storageData);
                var expires = storageData.expires;
                if (!expires || Date.now() < new Date(expires).getTime()) {
                    return storageData.data;
                }
                return null;
            },
            set: function (key, value, expires) {
                key = prefix + key;
                var oneDayMs = 24 * 3600 * 1000;
                var storageData = {
                    data: value,
                }
                if (expires) {
                    storageData.expires = new Date(Date.now() + expires * oneDayMs);
                }
                storageObj.setItem(key, JSON.stringify(storageData));
            },
            remove: function (key) {
                key = prefix + key;
                storageObj.removeItem(key);
            }
        }
    };
    // format date
    var _formatDateNum = function (num) {
        return num < 10 ? '0' + num : num;
    }
    /**
     * 格式化日期函数，支持2种格式的输出
     * @param {date} date 日期对象或时间戳
     * @param {boolean} onlyDate 只输入日期，不输出时间
     */
    var formatDate = function (date, onlyDate) {
        date = new Date(date);
        if (!date.getTime()) {
            return '';
        }
        var year = date.getFullYear();
        var month = date.getMonth() + 1;
        var day = date.getDate();
        var hour = _formatDateNum(date.getHours());
        var minute = _formatDateNum(date.getMinutes());
        var second = _formatDateNum(date.getSeconds());

        return [year, month, day].join('-') +
            (onlyDate ? '' : ' ' + [hour, minute, second].join(':'));
    };
    /**
     * 继承函数， 如果第一个参数为true，则是深度继承
     */
    var extend = function () {
        var args = arguments,
            target = args[0],   //  假设第一个参数为目标对象
            len = args.length,  //  获取参数总长度
            i = 1,              //  假设继承对象从下标为1开始
            deep = false,       //  初始化为浅拷贝
            tar, source, option, key;
        //  如果第一个参数是布尔值，那么第二个参数做为目标对象
        if (typeof target === 'boolean') {
            deep = target;
            target = args[i++];
        }
        //  遍历继承对象，并将每一个都继承到目标对象中
        for (; i < len; i++) {

            option = args[i];

            for (key in option) {
                tar = target[key];
                source = option[key];
                //  如果为深拷贝并且此时的属性值为对象，则进行递归拷贝
                if (deep && typeof source === 'object') {
                    if (!tar) {   //  如果目标对象没有此属性，那么创建它
                        tar = Object.prototype.call(source) === '[object Array]' ? [] : {};
                    }
                    //  将递归拷贝的结果赋值给目标对象
                    target[key] = extend(deep, tar, source);
                } else {
                    //  如果为浅拷贝，直接赋值
                    target[key] = source;
                }
            }
        }
        return target;
    };
    /**
     * 事件队列对象：订阅发布
     */
    var EventBus = function () {
        if (!(this instanceof EventBus)) {
            return new EventBus();
        }
        this.cache = {};
    };
    EventBus.prototype = {
        constructor: EventBus,
        on: function (eventType, handler, context) {
            var handleList = this.cache[eventType];
            if (!handleList) {
                handleList = [];
                this.cache[eventType] = handleList;
            }
            handleList.push({
                handler: handler,
                context: context
            });
        },
        emit: function () {
            var args = [].slice.call(arguments);
            var eventType = args[0];
            var handleList = this.cache[eventType];
            if (handleList) {
                handleList.forEach(function (handleData) {
                    handleData.handler.apply(handleData.context, args.slice(1));
                });
            }
        }
    }
    /**
     * 函数锁定，将函数进行上锁动作，用户手动解锁才能再次执行；
     * 使用场景：点击按钮发送请求，请求完毕后执行unlock解锁
     * @param {function} cb 要锁定的函数
     * @param {object} context 函数执行上下文
     */
    var lockHandler = function (cb,context) {
        var isLock = false;
        var slice = [].slice;

        var unlock = function () {
            isLock = false;
        }

        return function () {
            var args = slice.call(arguments, 0);
            if (isLock) {
                return false;
            }

            isLock = true;
            cb.apply(context, args.concat(unlock));
        }
    };
    /**
     * scrollTop滚动动画，在一定时间内滚动到一定的scrollTop
     * @param {number} scrollTop scrollTop结果值
     * @param {number} runtime 动画运行时间
     */
    var scrollAnimation = function (scrollTop, runtime) {
        var requestNewAnimationFrame = window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.setTimeout;
        var timeCount = 0; // 次数统计
        var timeout = 20; // 延迟毫秒数
        var loopTime = Math.floor(runtime / timeout); // 循环次数
        var currentScrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 当前scrollTop
        var middleScrollTop = scrollTop - currentScrollTop; // 差值
        var task = function () {
            timeCount += 1;
            let changeScrollTop = currentScrollTop + (middleScrollTop * timeCount / loopTime);
            document.documentElement.scrollTop = document.body.scrollTop = changeScrollTop;
            if (timeCount === loopTime) {
                return;
            }
            requestNewAnimationFrame(task, timeout);
        };
        requestNewAnimationFrame(task, timeout);
    };

    return {
        httpurl: parseUrl(location.href),
        parseUrl: parseUrl,
        serialize: serialize,
        cookie: cookie,
        countDown: countDown,
        formatTimeJson: formatTimeJson,
        storage: storage,
        formatDate: formatDate,
        extend: extend,
        EventBus: EventBus,
        lockHandler: lockHandler,
        scrollAnimation: scrollAnimation,
    };
});
